/*
 * Copyright (c) 2002, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.orb;

import com.sun.corba.se.impl.orbutil.GetPropertyAction;

import java.security.PrivilegedAction;
import java.security.AccessController;

import java.applet.Applet;

import java.util.Properties;
import java.util.Vector;
import java.util.Set;
import java.util.HashSet;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.StringTokenizer;

import java.net.URL;

import java.security.AccessController;

import java.io.File;
import java.io.FileInputStream;

import com.sun.corba.se.spi.orb.DataCollector;
import com.sun.corba.se.spi.orb.PropertyParser;

import com.sun.corba.se.impl.orbutil.ORBConstants;
import com.sun.corba.se.impl.orbutil.ORBUtility;

public abstract class DataCollectorBase implements DataCollector {

  private PropertyParser parser;
  private Set propertyNames;
  private Set propertyPrefixes;
  private Set URLPropertyNames;
  protected String localHostName;
  protected String configurationHostName;
  private boolean setParserCalled;
  private Properties originalProps;
  private Properties resultProps;

  public DataCollectorBase(Properties props, String localHostName,
      String configurationHostName) {
    // XXX This is fully initialized here.  So do we ever want to
    // generalize this (or perhaps this is the wrong place for this?)
    URLPropertyNames = new HashSet();
    URLPropertyNames.add(ORBConstants.INITIAL_SERVICES_PROPERTY);

    propertyNames = new HashSet();

    // Make sure that we are ready to handle -ORBInitRef.  This is special
    // due to the need to handle multiple -ORBInitRef args as prefix
    // parsing.
    propertyNames.add(ORBConstants.ORB_INIT_REF_PROPERTY);

    propertyPrefixes = new HashSet();

    this.originalProps = props;
    this.localHostName = localHostName;
    this.configurationHostName = configurationHostName;
    setParserCalled = false;
    resultProps = new Properties();
  }

//////////////////////////////////////////////////////////
// Public interface defined in DataCollector
//////////////////////////////////////////////////////////

  public boolean initialHostIsLocal() {
    checkSetParserCalled();
    return localHostName.equals(resultProps.getProperty(
        ORBConstants.INITIAL_HOST_PROPERTY));
  }

  public void setParser(PropertyParser parser) {
    Iterator iter = parser.iterator();
    while (iter.hasNext()) {
      ParserAction pa = (ParserAction) (iter.next());
      if (pa.isPrefix()) {
        propertyPrefixes.add(pa.getPropertyName());
      } else {
        propertyNames.add(pa.getPropertyName());
      }
    }

    collect();
    setParserCalled = true;
  }

  public Properties getProperties() {
    checkSetParserCalled();
    return resultProps;
  }

//////////////////////////////////////////////////////////
// public interface from DataCollector that must be defined
// in subclasses
//////////////////////////////////////////////////////////

  public abstract boolean isApplet();

//////////////////////////////////////////////////////////
// Implementation methods needed in subclasses
//////////////////////////////////////////////////////////

  protected abstract void collect();

//////////////////////////////////////////////////////////
// methods for use by subclasses
//////////////////////////////////////////////////////////

  protected void checkPropertyDefaults() {
    String host =
        resultProps.getProperty(ORBConstants.INITIAL_HOST_PROPERTY);

    if ((host == null) || (host.equals(""))) {
      setProperty(ORBConstants.INITIAL_HOST_PROPERTY,
          configurationHostName);
    }

    String serverHost =
        resultProps.getProperty(ORBConstants.SERVER_HOST_PROPERTY);

    if (serverHost == null ||
        serverHost.equals("") ||
        serverHost.equals("0.0.0.0") ||
        serverHost.equals("::") ||
        serverHost.toLowerCase().equals("::ffff:0.0.0.0")) {
      setProperty(ORBConstants.SERVER_HOST_PROPERTY,
          localHostName);
      setProperty(ORBConstants.LISTEN_ON_ALL_INTERFACES,
          ORBConstants.LISTEN_ON_ALL_INTERFACES);
    }
  }

  protected void findPropertiesFromArgs(String[] params) {
    if (params == null) {
      return;
    }

    // All command-line args are of the form "-ORBkey value".
    // The key is mapped to <prefix>.ORBkey.

    String name;
    String value;

    for (int i = 0; i < params.length; i++) {
      value = null;
      name = null;

      if (params[i] != null && params[i].startsWith("-ORB")) {
        String argName = params[i].substring(1);
        name = findMatchingPropertyName(propertyNames, argName);

        if (name != null) {
          if (i + 1 < params.length && params[i + 1] != null) {
            value = params[++i];
          }
        }
      }

      if (value != null) {
        setProperty(name, value);
      }
    }
  }

  protected void findPropertiesFromApplet(final Applet app) {
    // Cannot use propertyPrefixes here, since there is no
    // way to fetch properties by prefix from an Applet.
    if (app == null) {
      return;
    }

    PropertyCallback callback = new PropertyCallback() {
      public String get(String name) {
        return app.getParameter(name);
      }
    };

    findPropertiesByName(propertyNames.iterator(), callback);

    // Special Case:
    //
    // Convert any applet parameter relative URLs to an
    // absolute URL based on the Document Root. This is so HTML
    // URLs can be kept relative which is sometimes useful for
    // managing the Document Root layout.
    PropertyCallback URLCallback = new PropertyCallback() {
      public String get(String name) {
        String value = resultProps.getProperty(name);
        if (value == null) {
          return null;
        }

        try {
          URL url = new URL(app.getDocumentBase(), value);
          return url.toExternalForm();
        } catch (java.net.MalformedURLException exc) {
          // Just preserve the original (malformed) value:
          // the error will be handled later.
          return value;
        }
      }
    };

    findPropertiesByName(URLPropertyNames.iterator(),
        URLCallback);
  }

  private void doProperties(final Properties props) {
    PropertyCallback callback = new PropertyCallback() {
      public String get(String name) {
        return props.getProperty(name);
      }
    };

    findPropertiesByName(propertyNames.iterator(), callback);

    findPropertiesByPrefix(propertyPrefixes,
        makeIterator(props.propertyNames()), callback);
  }

  protected void findPropertiesFromFile() {
    final Properties fileProps = getFileProperties();
    if (fileProps == null) {
      return;
    }

    doProperties(fileProps);
  }

  protected void findPropertiesFromProperties() {
    if (originalProps == null) {
      return;
    }

    doProperties(originalProps);
  }

  //
  // Map System properties to ORB properties.
  // Security bug fix 4278205:
  // Only allow reading of system properties with ORB prefixes.
  // Previously a malicious subclass was able to read ANY system property.
  // Note that other prefixes are fine in other contexts; it is only
  // system properties that should impose a restriction.
  protected void findPropertiesFromSystem() {
    Set normalNames = getCORBAPrefixes(propertyNames);
    Set prefixNames = getCORBAPrefixes(propertyPrefixes);

    PropertyCallback callback = new PropertyCallback() {
      public String get(String name) {
        return getSystemProperty(name);
      }
    };

    findPropertiesByName(normalNames.iterator(), callback);

    findPropertiesByPrefix(prefixNames,
        getSystemPropertyNames(), callback);
  }

//////////////////////////////////////////////////////////
// internal implementation
//////////////////////////////////////////////////////////

  // Store name, value in resultProps, with special
  // treatment of ORBInitRef.  All updates to resultProps
  // must happen through this method.
  private void setProperty(String name, String value) {
    if (name.equals(ORBConstants.ORB_INIT_REF_PROPERTY)) {
      // Value is <name>=<URL>
      StringTokenizer st = new StringTokenizer(value, "=");
      if (st.countTokens() != 2) {
        throw new IllegalArgumentException();
      }

      String refName = st.nextToken();
      String refValue = st.nextToken();

      resultProps.setProperty(name + "." + refName, refValue);
    } else {
      resultProps.setProperty(name, value);
    }
  }

  private void checkSetParserCalled() {
    if (!setParserCalled) {
      throw new IllegalStateException("setParser not called.");
    }
  }

  // For each prefix in prefixes, For each name in propertyNames,
  // if (prefix is a prefix of name) get value from getProperties and
  // setProperty (name, value).
  private void findPropertiesByPrefix(Set prefixes,
      Iterator propertyNames, PropertyCallback getProperty) {
    while (propertyNames.hasNext()) {
      String name = (String) (propertyNames.next());
      Iterator iter = prefixes.iterator();
      while (iter.hasNext()) {
        String prefix = (String) (iter.next());
        if (name.startsWith(prefix)) {
          String value = getProperty.get(name);

          // Note: do a put even if value is null since just
          // the presence of the property may be significant.
          setProperty(name, value);
        }
      }
    }
  }

  // For each prefix in names, get the corresponding property
  // value from the callback, and store the name/value pair in
  // the result.
  private void findPropertiesByName(Iterator names,
      PropertyCallback getProperty) {
    while (names.hasNext()) {
      String name = (String) (names.next());
      String value = getProperty.get(name);
      if (value != null) {
        setProperty(name, value);
      }
    }
  }

  private static String getSystemProperty(final String name) {
    return (String) AccessController.doPrivileged(
        new GetPropertyAction(name));
  }

  // Map command-line arguments to ORB properties.
  //
  private String findMatchingPropertyName(Set names,
      String suffix) {
    Iterator iter = names.iterator();
    while (iter.hasNext()) {
      String name = (String) (iter.next());
      if (name.endsWith(suffix)) {
        return name;
      }
    }

    return null;
  }

  private static Iterator makeIterator(final Enumeration enumeration) {
    return new Iterator() {
      public boolean hasNext() {
        return enumeration.hasMoreElements();
      }

      public Object next() {
        return enumeration.nextElement();
      }

      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  private static Iterator getSystemPropertyNames() {
    // This will not throw a SecurityException because this
    // class was loaded from rt.jar using the bootstrap classloader.
    Enumeration enumeration = (Enumeration)
        AccessController.doPrivileged(
            new PrivilegedAction() {
              public java.lang.Object run() {
                return System.getProperties().propertyNames();
              }
            }
        );

    return makeIterator(enumeration);
  }

  private void getPropertiesFromFile(Properties props, String fileName) {
    try {
      File file = new File(fileName);
      if (!file.exists()) {
        return;
      }

      FileInputStream in = new FileInputStream(file);

      try {
        props.load(in);
      } finally {
        in.close();
      }
    } catch (Exception exc) {
      // if (ORBInitDebug)
      // dprint( "ORB properties file " + fileName + " not found: " +
      // exc) ;
    }
  }

  private Properties getFileProperties() {
    Properties defaults = new Properties();

    String javaHome = getSystemProperty("java.home");
    String fileName = javaHome + File.separator + "lib" + File.separator +
        "orb.properties";

    getPropertiesFromFile(defaults, fileName);

    Properties results = new Properties(defaults);

    String userHome = getSystemProperty("user.home");
    fileName = userHome + File.separator + "orb.properties";

    getPropertiesFromFile(results, fileName);
    return results;
  }

  private boolean hasCORBAPrefix(String prefix) {
    return prefix.startsWith(ORBConstants.ORG_OMG_PREFIX) ||
        prefix.startsWith(ORBConstants.SUN_PREFIX) ||
        prefix.startsWith(ORBConstants.SUN_LC_PREFIX) ||
        prefix.startsWith(ORBConstants.SUN_LC_VERSION_PREFIX);
  }

  // Return only those element of prefixes for which hasCORBAPrefix
  // is true.
  private Set getCORBAPrefixes(final Set prefixes) {
    Set result = new HashSet();
    Iterator iter = prefixes.iterator();
    while (iter.hasNext()) {
      String element = (String) (iter.next());
      if (hasCORBAPrefix(element)) {
        result.add(element);
      }
    }

    return result;
  }
}

// Used to collect properties from various sources.
abstract class PropertyCallback {

  abstract public String get(String name);
}
